﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;

namespace Groovy_IM.Protocols.AIM
{
    public class Sock
    {
        private Socket m_Socket;
        private byte[] m_RecvHeader;
        private int m_RecvOffset;
        public int State;
        private static DnsEndPoint IP_END_POINT;
        private SocketAsyncEventArgs sendEventArgs;

        private ushort ClientSeq = 0;

        public delegate void Closed();
        public delegate void Connected();
        public delegate void OnArrival(FLAPSocket packet);

        public event Closed OnClose;
        public event Connected OnConnect;
        public event OnArrival DataArrival;

        public void beginConnect(string Host, int Port)
        {
            IP_END_POINT = new DnsEndPoint(Host, Port);

            m_RecvHeader = new byte[6];

            m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            SocketAsyncEventArgs connectEventArgs = new SocketAsyncEventArgs();

            connectEventArgs.RemoteEndPoint = IP_END_POINT;

            connectEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(endConnect);

            m_Socket.ConnectAsync(connectEventArgs);
        }

        public void Close()
        {
            if (State == 0)
                return;

            m_Socket.Close();

            State = 0;

            if (OnClose != null)
                OnClose();
        }

        private void endConnect(object sender, SocketAsyncEventArgs e)
        {
            sendEventArgs = new SocketAsyncEventArgs();
            sendEventArgs.RemoteEndPoint = IP_END_POINT;

            if (OnConnect != null)
                OnConnect();

            State = 1;

            e.Completed -= new EventHandler<SocketAsyncEventArgs>(endConnect);
            e.Completed += new EventHandler<SocketAsyncEventArgs>(EndReceive);

            beginReceive(sender, e);
        }

        private void beginReceive(object sender, SocketAsyncEventArgs e)
        {
            e.Completed -= new EventHandler<SocketAsyncEventArgs>(EndReceive);
            e.Completed += new EventHandler<SocketAsyncEventArgs>(EndReceive);

            m_RecvHeader = new byte[8132];

            e.SetBuffer(m_RecvHeader, m_RecvOffset, m_RecvHeader.Length - m_RecvOffset);

            m_Socket.ReceiveAsync(e);
        }

        private void EndReceive(object sender, SocketAsyncEventArgs e)
        {
            try
            {
                if (e.BytesTransferred >= 6)
                {
                    for (int x = 0; x < e.BytesTransferred; )
                    {
                        if (e.Buffer[x + 0] == 0x2a)
                        {
                            FLAPSocket packet = new FLAPSocket();
                            packet.Channel = (FLAPChannel)e.Buffer[x + 1];
                            packet.Data = new byte[Tools.GetWord(e.Buffer, x + 4)];

                            Array.Copy(e.Buffer, x + 6, packet.Data, 0, packet.Data.Length);

                            if (DataArrival != null)
                                DataArrival(packet);

                            x += (6 + packet.Data.Length);
                        }
                        else
                            x++;
                    }
                }

                beginReceive(sender, e);
            }
            catch (Exception)
            {
                Close();
            }
        }

        private ushort GetNextSeq()
        {
            if (ClientSeq >= 65535)
                ClientSeq = 0;
            else
                ClientSeq++;

            return ClientSeq;
        }

        public void SendData(FLAPSocket packet)
        {
            ushort packetLength = 0;

            byte[] header = new byte[6];

            header[0] = 42;

            header[1] = (byte)packet.Channel;

            Tools.PutWord(header, 2, GetNextSeq());

            if (packet.Data != null)
                packetLength = (ushort)packet.Data.Length;

            Tools.PutWord(header, 4, (ushort)packetLength);

            byte[] tmpPacket = new byte[header.Length + packetLength];

            Buffer.BlockCopy(header, 0, tmpPacket, 0, header.Length);

            if (packet.Data != null)
                Buffer.BlockCopy(packet.Data, 0, tmpPacket, header.Length, packet.Data.Length);

            try
            {
                sendEventArgs = new SocketAsyncEventArgs();

                sendEventArgs.RemoteEndPoint = IP_END_POINT;

                List<ArraySegment<byte>> l = new List<ArraySegment<byte>>();
                l.Add(new ArraySegment<byte>(tmpPacket));

                sendEventArgs.BufferList = l;
                m_Socket.SendAsync(sendEventArgs);
            }
            catch (Exception)
            {
                Close();
            }
        }
    }

    public class FLAPSocket
    {
        public FLAPChannel Channel = FLAPChannel.SNAC;
        public byte[] Data;
        public int Offset;
    }

    public enum FLAPChannel
    {
        Connect = 0x01,
        SNAC = 0x02,
        Error = 0x03,
        Disconnect = 0x04,
        KeepAlive = 0x05
    }
}
